package com.googlecode.dgwt.client.dojo.util;

import java.io.Serializable;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;

import com.google.gwt.core.client.GWT;
import com.googlecode.dgwt.client.DGWT;
import com.googlecode.dgwt.client.dojo.core.JsArray;

/**
 * @author wellsantos@wellsantos.com
 * @since 0.01
 */
public class NativeList<Item> extends AbstractList<Item> implements Serializable {

    private static final long serialVersionUID = 1L;
    
    private transient ArrayList<Item> proxy;
    private transient JsArray<Item> array;
    private transient int size;
    
    // FIXME GWT does not recognize the inherited variable modCount 
    protected transient int modCount;
    
    public NativeList() {
        this(10);
    }
    
    public NativeList(int capacity) {
        if (!GWT.isScript() && DGWT.isOptimizeCollections()){
            proxy = new ArrayList<Item>(capacity);
            return;
        }
        array = JsArray.create(capacity);
    }
    
    public NativeList(Collection<? extends Item> list) {
        this(list.size());
        addAll(list);
    }
    
    protected boolean isProxy(){
        return proxy != null;
    }

    private native int nativeIndexOf(Object obj) /*-{
        var vet=this.@com.googlecode.dgwt.client.dojo.util.NativeList::array,
            size=this.@com.googlecode.dgwt.client.dojo.util.NativeList::size;
        for (var i=0; i<size; i++){
            if (@com.googlecode.dgwt.client.dojo.Dojo::isEquals(Ljava/lang/Object;Ljava/lang/Object;)(obj, vet[i])){
                return i;
            }
        }
        return -1;
    }-*/;
    
    @Override
    public int indexOf(Object obj) {
        if (isProxy()){
            return proxy.indexOf(obj);
        }
        return nativeIndexOf(obj);
    }

    @Override
    public Item get(int index) {
        if (isProxy()){
            return proxy.get(index);
        }
        return array.get(index);
    }
    
    @Override
    public int size() {
        if (isProxy()){
            return proxy.size();
        }
        return size;
    }
    
    public void setSize(int newSize){
        assert(newSize > -1);
        
        if (isProxy()){
            while (size() < newSize){
                add(null);
            }
            while (size() > newSize){
                remove(size()-1);
            }
            return;
        }
        
        if (size != newSize){
            size = newSize;
            modCount++;
        }
    }
    
    @Override
    public boolean contains(Object item) {
        return indexOf(item) >= 0;
    }
    
    @Override
    public boolean add(Item item) {
        add(size(), item);
        return true;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void add(int index, Item item) {
        if (isProxy()){
            proxy.add(index, item);
            return;
        }
        if (index < 0 || index > size){
            throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
        }
        if (index == size){
            array.set(size, item);
        } else {
            array.splice(index, 0, item);
        }
        modCount++;
        size++;
    }
    
    @Override
    public Item set(int index, Item item) {
        if (isProxy()){
            return proxy.set(index, item);
        }
        if (index < 0 || index >= size){
            throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
        }
        modCount++;
        Item oldItem = array.get(index);
        array.set(index, item);
        return oldItem;
    }    
    
    @Override
    public Item remove(int index) {
        if (isProxy()){
            return proxy.remove(index);
        }
        if (index < 0 || index >= size){
            throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
        }
        Item oldItem = array.get(index);
        array.remove(index);
        modCount++;
        size--;
        return oldItem;
    }
    
    @Override
    public boolean remove(Object item) {
        int index = indexOf(item);
        if (index >= 0) {
            remove(index);
            return true;
        }
        return false;
    }

}
